package com.xx.permission.config;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.xx.permission.entity.result.DataPermission;
import com.xx.permission.utils.ExpressionUtils;
import com.xx.permission.utils.UserUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

/**
 * @author aqi
 * @describe 数据权限拦截器
 */
@Slf4j
@Component
public class DataScopeInterceptor extends JsqlParserSupport implements InnerInterceptor {

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();

        if (sct == SqlCommandType.SELECT) {
            if (this.judgementDataScopeIgnore(ms)) {
                return;
            }

            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(), null));
        }
    }

    /**
     * 查询
     */
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        SelectBody selectBody = select.getSelectBody();
        PlainSelect plainSelect = (PlainSelect) selectBody;

        // 获取表名/别名(如果是关联查询是取第一个join左侧的表名/别名)
        String tableName = ExpressionUtils.getTableName(plainSelect);

        // 构建用户权限控制条件
        this.buildUserPermissionSql(plainSelect, sql, tableName);
    }

    /**
     * 构建用户权限控制条件
     * 思路:
     *      1、获取当前用户信息,再通过用户信息获取到角色对应的数据权限集合
     *      2、通过获取到的该用户所有的数据权限集合过滤出本次请求的数据权限
     *      3、将本次请求的数据权限拼接到已有的SQL中
     * @param plainSelect 用于解析SQL的类
     * @param tableName 表名/别名(join查询左侧表名)
     */
    private void buildUserPermissionSql(PlainSelect plainSelect, String sql, String tableName) {
        // 获取当前用户接口数据权限(这里的数据都是模拟的,实际上可能得从缓存或者session中获取)
        Map<String, List<DataPermission>> userPermissionMap = UserUtils.getUserPermission();
        // 获取本次请求的uri
        String uri = UserUtils.getUri();
        // 获取本次请求uri对应的数据权限集合
        List<DataPermission> dataPermissions = userPermissionMap.get(uri);
        if (!CollectionUtils.isEmpty(dataPermissions)) {
            // 将多个条件合并在一起
            dataPermissions.forEach(permission -> {
                InExpression inExpression = ExpressionUtils.buildInSql(tableName, permission);
                ExpressionUtils.appendExpression(plainSelect, inExpression);
            });
        }
        log.info("[DataScopeInterceptor]请求uri:[{}],原始SQL:[{}]处理后SQL:[{}]", uri, sql, plainSelect);
    }

    /**
     * 判断类/方法上是否存在DataScopeIgnore注解
     * 存在的问题:这里只能获取到Mapper层中的一些东西,所以在别的地方加@DataScopeIgnore也不会生效
     * @param ms ms
     * @return 是否需要进行数据权限控制,true:不需要,false:需要
     */
    private boolean judgementDataScopeIgnore(MappedStatement ms) {
        try {
            String id = ms.getId();

            String classPath = id.substring(0, id.lastIndexOf("."));
            Class<?> aClass = Class.forName(classPath);

            // 判断该类上是否存在DataScopeIgnore注解
            DataScopeIgnore declaredClassAnnotation = aClass.getDeclaredAnnotation(DataScopeIgnore.class);

            if (declaredClassAnnotation != null) {
                return true;
            }

            // 判断该方法上是否存在DataScopeIgnore注解
            String methodName = id.substring(id.lastIndexOf(".") + 1);
            Method method = aClass.getMethod(methodName);
            DataScopeIgnore declaredMethodAnnotation = method.getDeclaredAnnotation(DataScopeIgnore.class);

            return declaredMethodAnnotation != null;
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            // 解析DataScopeIgnore出现异常,默认当作需要做数据权限控制
            return false;
        }
    }

}
